/*******************************************************************************
 * This file is part of Crunch Graphics.
 *
 * Crunch Graphics is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * Crunch Graphics is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * Crunch Graphics.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package com.crunch.graphics.gpu;

import javax.media.opengl.GL;

/** Encapsulates different types of draw calls on the GPU.
 */
public class GpuDrawCall {
	/** Encapsulates a draw call on the GPU.
	 */
	public static class DrawCall extends GpuObject {
		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int firstVertex, int vertexCount) {
			mode = drawMode;
			first = firstVertex;
			vertices = vertexCount;
		}

		/** Performs the draw call.
		 */
		public void draw() {
			getGpuState().getGl().glDrawArrays(mode, first, vertices);
		}

		DrawCall(GpuState state) {
			super(state);
			mode = GL.GL_TRIANGLES;
			first = 0;
			vertices = 0;
		}

		/** Copy constructor.
		 */
		public DrawCall(DrawCall other) {
			super(other.getGpuState());
			mode = other.mode;
			first = other.first;
			vertices = other.vertices;
		}

		private int mode;
		private int first;
		private int vertices;
	}

	/** Encapsulates an instanced draw call on the GPU.
	 */
	public static class InstancedDrawCall extends GpuObject {
		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int firstVertex, int vertexCount, int instanceCount) {
			mode = drawMode;
			first = firstVertex;
			vertices = vertexCount;
			instances = instanceCount;
		}

		/** Performs the draw call.
		 */
		public void draw() {
			getGpuState().getGl().glDrawArraysInstanced(mode, first, vertices, instances);
		}

		InstancedDrawCall(GpuState state) {
			super(state);
			mode = GL.GL_TRIANGLES;
			first = 0;
			vertices = 0;
			instances = 0;
		}

		/** Copy constructor.
		 */
		public InstancedDrawCall(InstancedDrawCall other) {
			super(other.getGpuState());
			mode = other.mode;
			first = other.first;
			vertices = other.vertices;
			instances = other.instances;
		}

		private int mode;
		private int first;
		private int vertices;
		private int instances;
	}

	/** Encapsulates an indexed draw call on the GPU.
	 */
	public static class IndexedDrawCall extends GpuObject {
		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int indexCount, int indexType, long indexPointer) {
			mode = drawMode;
			indices = indexCount;
			type = indexType;
			pointer = indexPointer;
			restart = false;
		}

		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int indexCount, int indexType, long indexPointer,
		                          int primitiveRestartIndex) {
			mode = drawMode;
			indices = indexCount;
			type = indexType;
			pointer = indexPointer;
			restart = true;
			restartIndex = primitiveRestartIndex;
		}

		/** Performs the draw call.
		 */
		public void draw() {
			GpuDrawState drawState = getGpuState().getCurrentDrawState();
			drawState.setPrimitiveRestartEnabled(restart);
			drawState.setPrimitiveRestartIndex(restartIndex);
			drawState.bind();
			getGpuState().getGl().glDrawElements( mode, indices, type, pointer);
		}

		IndexedDrawCall(GpuState state) {
			super(state);
			mode = GL.GL_TRIANGLES;
			indices = 0;
			type = GL.GL_UNSIGNED_INT;
			pointer = 0;
			restart = false;
			restartIndex = 0;
		}

		/** Copy constructor.
		 */
		public IndexedDrawCall(IndexedDrawCall other) {
			super(other.getGpuState());
			mode = other.mode;
			indices = other.indices;
			type = other.type;
			pointer = other.pointer;
			restart = other.restart;
			restartIndex = other.restartIndex;
		}

		private int mode;
		private int indices;
		private int type;
		private long pointer;
		private boolean restart;
		private int restartIndex;
	}

	/** Encapsulates an indexed offset draw call on the GPU.
	 */
	public static class IndexedOffsetDrawCall extends GpuObject {
		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int indexCount, int indexType, long indexPointer, int vertexOffset) {
			mode = drawMode;
			indices = indexCount;
			type = indexType;
			pointer = indexPointer;
			offset = vertexOffset;
			restart = false;
		}

		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int indexCount, int indexType, long indexPointer, int vertexOffset,
		                          int primitiveRestartIndex) {
			mode = drawMode;
			indices = indexCount;
			type = indexType;
			pointer = indexPointer;
			offset = vertexOffset;
			restart = true;
			restartIndex = primitiveRestartIndex;
		}

		/** Performs the draw call.
		 */
		public void draw() {
			GpuDrawState drawState = getGpuState().getCurrentDrawState();
			drawState.setPrimitiveRestartEnabled(restart);
			drawState.setPrimitiveRestartIndex(restartIndex);
			drawState.bind();
			getGpuState().getGl().glDrawElementsBaseVertex(mode, indices, type, pointer, offset);
		}

		IndexedOffsetDrawCall(GpuState state) {
			super(state);
			mode = GL.GL_TRIANGLES;
			indices = 0;
			type = GL.GL_UNSIGNED_INT;
			pointer = 0;
			offset = 0;
			restart = false;
			restartIndex = 0;
		}

		/** Copy constructor.
		 */
		public IndexedOffsetDrawCall(IndexedOffsetDrawCall other) {
			super(other.getGpuState());
			mode = other.mode;
			indices = other.indices;
			type = other.type;
			pointer = other.pointer;
			offset = other.offset;
			restart = other.restart;
			restartIndex = other.restartIndex;
		}

		private int mode;
		private int indices;
		private int type;
		private long pointer;
		private int offset;
		private boolean restart;
		private int restartIndex;
	}

	/** Encapsulates an indexed instanced draw call on the GPU.
	 */
	public static class IndexedInstancedDrawCall extends GpuObject {
		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int indexCount, int indexType, long indexPointer, int instanceCount) {
			mode = drawMode;
			indices = indexCount;
			type = indexType;
			pointer = indexPointer;
			instances = instanceCount;
			restart = false;
		}

		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int indexCount, int indexType, long indexPointer, int instanceCount,
		                          int primitiveRestartIndex) {
			mode = drawMode;
			indices = indexCount;
			type = indexType;
			pointer = indexPointer;
			instances = instanceCount;
			restart = true;
			restartIndex = primitiveRestartIndex;
		}

		/** Performs the draw call.
		 */
		public void draw() {
			GpuDrawState drawState = getGpuState().getCurrentDrawState();
			drawState.setPrimitiveRestartEnabled(restart);
			drawState.setPrimitiveRestartIndex(restartIndex);
			drawState.bind();
			getGpuState().getGl().glDrawElementsInstanced(mode, indices, type, pointer, instances);
		}

		IndexedInstancedDrawCall(GpuState state) {
			super(state);
			mode = GL.GL_TRIANGLES;
			indices = 0;
			type = GL.GL_UNSIGNED_INT;
			pointer = 0;
			instances = 0;
			restart = false;
			restartIndex = 0;
		}

		/** Copy constructor.
		 */
		public IndexedInstancedDrawCall(IndexedInstancedDrawCall other) {
			super(other.getGpuState());
			mode = other.mode;
			indices = other.indices;
			type = other.type;
			pointer = other.pointer;
			instances = other.instances;
			restart = other.restart;
			restartIndex = other.restartIndex;
		}

		private int mode;
		private int indices;
		private int type;
		private long pointer;
		private int instances;
		private boolean restart;
		private int restartIndex;
	}

	/** Encapsulates an indexed instanced offset draw call on the GPU.
	 */
	public static class IndexedInstancedOffsetDrawCall extends GpuObject {
		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int indexCount, int indexType, long indexPointer, int instanceCount, int vertexOffset) {
			mode = drawMode;
			indices = indexCount;
			type = indexType;
			pointer = indexPointer;
			instances = instanceCount;
			offset = vertexOffset;
			restart = false;
		}

		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int indexCount, int indexType, long indexPointer, int instanceCount, int vertexOffset,
		                          int primitiveRestartIndex) {
			mode = drawMode;
			indices = indexCount;
			type = indexType;
			pointer = indexPointer;
			instances = instanceCount;
			offset = vertexOffset;
			restart = false;
			restartIndex = primitiveRestartIndex;
		}

		/** Performs the draw call.
		 */
		public void draw() {
			GpuDrawState drawState = getGpuState().getCurrentDrawState();
			drawState.setPrimitiveRestartEnabled(restart);
			drawState.setPrimitiveRestartIndex(restartIndex);
			drawState.bind();
			getGpuState().getGl().glDrawElementsInstancedBaseVertex(mode, indices, type, pointer, instances, offset);
		}

		IndexedInstancedOffsetDrawCall(GpuState state) {
			super(state);
			mode = GL.GL_TRIANGLES;
			indices = 0;
			type = GL.GL_UNSIGNED_INT;
			pointer = 0;
			instances = 0;
			offset = 0;
			restart = false;
			restartIndex = 0;
		}

		/** Copy constructor.
		 */
		public IndexedInstancedOffsetDrawCall(IndexedInstancedOffsetDrawCall other) {
			super(other.getGpuState());
			mode = other.mode;
			indices = other.indices;
			type = other.type;
			pointer = other.pointer;
			instances = other.instances;
			offset = other.offset;
			restart = other.restart;
			restartIndex = other.restartIndex;
		}

		private int mode;
		private int indices;
		private int type;
		private long pointer;
		private int instances;
		private int offset;
		private boolean restart;
		private int restartIndex;
	}

	/** Encapsulates an indexed ranged draw call on the GPU.
	 */
	public static class IndexedRangedDrawCall extends GpuObject {
		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int smallestIndex, int largestIndex, int indexCount, int indexType, long indexPointer) {
			mode = drawMode;
			smallest = smallestIndex;
			largest = largestIndex;
			indices = indexCount;
			type = indexType;
			pointer = indexPointer;
			restart = false;
		}

		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int smallestIndex, int largestIndex, int indexCount, int indexType, long indexPointer,
		                          int primitiveRestartIndex) {
			mode = drawMode;
			smallest = smallestIndex;
			largest = largestIndex;
			indices = indexCount;
			type = indexType;
			pointer = indexPointer;
			restart = true;
			restartIndex = primitiveRestartIndex;
		}

		/** Performs the draw call.
		 */
		public void draw() {
			GpuDrawState drawState = getGpuState().getCurrentDrawState();
			drawState.setPrimitiveRestartEnabled(restart);
			drawState.setPrimitiveRestartIndex(restartIndex);
			drawState.bind();
			getGpuState().getGl().glDrawRangeElements(mode, smallest, largest, indices, type, pointer);
		}

		IndexedRangedDrawCall(GpuState state) {
			super(state);
			mode = GL.GL_TRIANGLES;
			smallest = 0;
			largest = 0;
			indices = 0;
			type = GL.GL_UNSIGNED_INT;
			pointer = 0;
			restart = false;
			restartIndex = 0;
		}

		/** Copy constructor.
		 */
		public IndexedRangedDrawCall(IndexedRangedDrawCall other) {
			super(other.getGpuState());
			mode = other.mode;
			smallest = other.smallest;
			largest = other.largest;
			indices = other.indices;
			type = other.type;
			pointer = other.pointer;
			restart = other.restart;
			restartIndex = other.restartIndex;
		}

		private int mode;
		private int smallest;
		private int largest;
		private int indices;
		private int type;
		private long pointer;
		private boolean restart;
		private int restartIndex;
	}

	/** Encapsulates an indexed ranged offset draw call on the GPU.
	 */
	public static class IndexedRangedOffsetDrawCall extends GpuObject {
		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int smallestIndex, int largestIndex, int indexCount, int indexType, long indexPointer, int vertexOffset) {
			mode = drawMode;
			smallest = smallestIndex;
			largest = largestIndex;
			indices = indexCount;
			type = indexType;
			pointer = indexPointer;
			offset = vertexOffset;
			restart = false;
		}

		/** Sets the draw call parameters.
		 */
		public void setParameters(int drawMode, int smallestIndex, int largestIndex, int indexCount, int indexType, long indexPointer, int vertexOffset,
		                          int primitiveRestartIndex) {
			mode = drawMode;
			smallest = smallestIndex;
			largest = largestIndex;
			indices = indexCount;
			type = indexType;
			pointer = indexPointer;
			offset = vertexOffset;
			restart = true;
			restartIndex = primitiveRestartIndex;
		}

		/** Performs the draw call.
		 */
		public void draw() {
			GpuDrawState drawState = getGpuState().getCurrentDrawState();
			drawState.setPrimitiveRestartEnabled(restart);
			drawState.setPrimitiveRestartIndex(restartIndex);
			drawState.bind();
			getGpuState().getGl().glDrawRangeElementsBaseVertex(mode, smallest, largest, indices, type, pointer, offset);
		}

		IndexedRangedOffsetDrawCall(GpuState state) {
			super(state);
			mode = GL.GL_TRIANGLES;
			smallest = 0;
			largest = 0;
			indices = 0;
			type = GL.GL_UNSIGNED_INT;
			pointer = 0;
			offset = 0;
			restart = false;
			restartIndex = 0;
		}

		/** Copy constructor.
		 */
		public IndexedRangedOffsetDrawCall(IndexedRangedOffsetDrawCall other) {
			super(other.getGpuState());
			mode = other.mode;
			smallest = other.smallest;
			largest = other.largest;
			indices = other.indices;
			type = other.type;
			pointer = other.pointer;
			offset = other.offset;
			restart = other.restart;
			restartIndex = other.restartIndex;
		}

		private int mode;
		private int smallest;
		private int largest;
		private int indices;
		private int type;
		private long pointer;
		private int offset;
		private boolean restart;
		private int restartIndex;
	}

	/** Encapsulates a clear call on the GPU.
	 */
	public static class ClearCall extends GpuObject {
		/** Sets the clear color.
		 */
		public void setClearColor(float[] color) {
			System.arraycopy(color, 0, clearColor, 0, 4);
		}

		/** Sets the clear depth.
		 */
		public void setClearDepth(float depth) {
			clearDepth = depth;
		}

		/** Sets the clear stencil.
		 */
		public void setClearStencil(int stencil) {
			clearStencil = stencil;
		}

		/** Sets the clear buffers.
		 */
		public void setClearBuffers(int buffers) {
			clearBuffers = buffers;
		}

		/** Performs the draw call.
		 */
		public void clear() {
			GpuDrawState drawState = getGpuState().getCurrentDrawState();
			if ((clearBuffers & GL.GL_COLOR_BUFFER_BIT) != 0) {
				drawState.setClearColor(clearColor);
			}
			if ((clearBuffers & GL.GL_DEPTH_BUFFER_BIT) != 0) {
				drawState.setClearDepth(clearDepth);
			}
			if ((clearBuffers & GL.GL_STENCIL_BUFFER_BIT) != 0) {
				drawState.setClearStencil(clearStencil);
			}
			drawState.bind();
			getGpuState().getGl().glClear(clearBuffers);
		}

		ClearCall(GpuState state) {
			super(state);
			clearDepth = 1.0f;
			clearStencil = 0;
			clearBuffers = 0;
		}

		/** Copy constructor.
		 */
		public ClearCall(ClearCall other) {
			super(other.getGpuState());
			clearDepth = other.clearDepth;
			clearStencil = other.clearStencil;
			clearBuffers = other.clearBuffers;
		}

		private float[] clearColor = new float[4];
		private float clearDepth;
		private int clearStencil;
		private int clearBuffers;
	}
}